
"""*****************************************************************************
                                  WARNING
   Copyright 2012 Hamilton Sundstrand Corporation. This document is the
   property of Hamilton Sundstrand Corporation ("HSC"). You may not possess,
   use, copy or disclose this document or any information in it, for any 
   purpose, including without limitation, to design, manufacture or repair 
   parts, or obtain any government approval to do so, without HSC's express 
   written permission. Neither receipt nor possession of this document
   alone, from any source, constitutes such permission. Possession, use,
   copying or disclosure by anyone without HSC's express written permission
   is not authorized and may result in criminal and/or civil liability.
********************************************************************************

                       ********************************
                       *                              *
                       *           kf_lib             *
                       *                              *
                       ********************************

Author(s):  Kevin Fritz
********************************************************************************
Description:

kf_lib provides a set of common functions and classes to simplify Scripting in 
Python . The main purpose for this library is to enhance and simplify the lower-level 
interface to excel through the readExcelTable and createExcelTable classes. These
classes require the installation of the common libraries: xlrd and xlwt. 


********************************************************************************
Revision History
*******************************************************************************/
Rev     Author          Date        Description
-------------------------------------------------------------------------------
v1.7    Kevin Fritz     1/12/11     Base
v1.8    Kevin Fritz     3/11/11     Added CSV support, Modified readExcelTable 
v2.0    Kevin Fritz     6/14/11     Added obj support for read/write to excel
                                    Added transpose function
                                    Added sort function
                                    Removed numArraySort function
v2.1    Kevin Fritz     5/23/12     Modified BufferTypeRW to allow more input types
                                    Modified Obj to accommodate  list of obj conversion
                                    Added XMl_node function for easily creating xml data
                                    Modified readExcelTable, bugfix on colstart
                                    Modified createExcelTable formula and merge support
                                    read/write file added error and check_path support
/***************************************************************************"""
__version__ = 2.2

import struct,copy,binascii, time, re #import standard python libraries
import xml.dom.minidom as XML

#---------------------------------------------------------------------------------------------------    
#General
def trim(string): #removes leading and trailing spaces from a string
    if len(string)>0:
        if string[0] in [' ','\n']: return trim(string[1:])
        elif string[-1] in [' ','\n']: return trim(string[:-1])
    return string

#---------------------------------------------------------------------------------------------------    
#Interface to Windows OS
def getWin32ClipboardText(): 
    import win32clipboard as w 
    import win32con
    w.OpenClipboard() 
    d=w.GetClipboardData(win32con.CF_TEXT) 
    w.CloseClipboard() 
    return d 
 
def setWin32ClipboardText(aType,aString): 
    import win32clipboard as w 
    import win32con
    w.OpenClipboard()
    w.EmptyClipboard()
    w.SetClipboardData(aType,aString) 
    w.CloseClipboard()

def checkpath(filename): #simple utility which checks if all folders are created for a path
    import os
    if filename.rfind("\\") != (-1) :
        folder=filename[:filename.rfind("\\")]
        if os.access(folder, os.F_OK) :
            #print 1, filename
            return filename
        else:
            try:
                os.mkdir(folder)
                return filename
            except:
                a=checkpath(folder[:folder.rfind("\\")] + "\\") #edit by Udit
                #print 2, filename
                #print folder+" p "
                return checkpath(filename) #edit by Udit
                #return filename #edit by Udit
    else:
        return filename

def findFiles(path, option):
    #This function recursively scans through the directory structure to find all
    # files or folders matching the criteria of the variable option, as defined below
    
    # if option = a list of strings: return a list of all files with these extensions
    # if option = a string with "." in it, return a list of all files with that extension
    # if option = "All_Folders": return a list of the paths for all folders and subfolders
    # if option = "Leaf_Folders": return a list of the paths for folders which do not contain folders
    # returns a list of the file paths
    import os
    if path[-1] == "\\": path = path[:-1] #strip off the last backslash
    result=[] # a list containing all file paths found during execution
    filetypes=[] # a list containing all extension being looked for (in lowercase)
    if type(option)==list: # if searching for folders, make list case-insensitive
        for ext in option: # iterate over all extensions passed in
            filetypes+= [ext.lower()] # convert to lowercase
    elif type(option)==str and "." in option: # if option is a string and contains a period
        filetypes = [option] # convert option to a single-element list of string
    if "*.*" in filetypes: # if searching for all files
        filetypes=["."] # then just look for a "."

    for filex in os.listdir(path): # iterate over every file or folder in current directory
        if len(filetypes) > 0: # if option specifies a list of extensions then: 
            if filex[filex.rfind("."):] in filetypes: # if current entry is a file whose extension is in filetypes:
                result+=[path+"\\"+filex] # add the full path to the results list
            elif "." not in filex: # if this is a folder
                result += findFiles(path+"\\"+filex, option) # get list of all elements in subfolder
        elif option.lower() == "all_folders": # option specifies all folders
            if "." not in filex: # filex is a folder
                result += [path+"\\"+filex] + findFiles(path+"\\"+filex, option) # add current path and all sub-folders
        elif option.lower() =="leaf_folders": # option specifying only folders that contain only files
            if "." not in filex:  # filex is a folder
                nestedfolders = findFiles(path+"\\"+filex, option) # get list of all sub folders
                # IF there are no sub-folders, add current path, ELSE add all sub-folders to result
                result+= nestedfolders if nestedfolders != [] else [path+"\\"+filex]
    return result

#---------------------------------------------------------------------------------------------------    
#Data Types
class obj(): #Creates an empty data structure, adds attributes
    #credit: http://mail.python.org/pipermail/python-bugs-list/2007-January/036866.html
    def __init__(self,*args,**kwargs):
        for arg in args:
            arg = copy.copy(arg)
            if type(arg) == dict: kwargs.update(arg) #argument is dictionary, convert
            elif type(arg) == obj: kwargs.update(arg.dict()) #argument is obj, copy to self
            elif type(arg) == list and len(args) == 1: #argument is list with one entry
                return [obj(x) for x in arg] #return list, recursivly calling self on each element
        
        self.__dict__=kwargs #append all keyword argument(s) to self, if any
    def __str__(self): return "lib.obj(%r)" % self.__dict__
    def __repr__(self): return "lib.obj(%r)" % self.__dict__
    def dict(self): #return a dictionary version of self
        return self.__dict__

#---------------------------------------------------------------------------------------------------    
#Log and Console interface    
class printBoth():
    #provides a class for printing to both console and log at once
    def __init__(self, logfn):
        self.txt=""
        self.logfn=logfn
            
    def bprint(self,*text):
        for x in list(text) + ["\n"]: print x,
        self.txt+= "%s\n" % "\t".join([str(x) for x in text])
    def bp(self,*text): self.bprint(*text)

    def close(self):
        l=open(self.logfn,'w')
        l.flush()
        l.write(self.txt)
        l.close()      

#---------------------------------------------------------------------------------------------------    
#code algorithms for working with lists

def createNDarray(dims, default=None): # creates an N-dimensional array, given an array of the dims
    #creates an N-dimensional array of type default
    if dims==[] or type(dims) != list:
        return default
    else:
        result=[]
        for i in range(dims[0]): result+=[createNDarray(dims[1:], default)]
        return result

def chunk(array, interval, combine_fnc=lambda x:x): #breaks a list into chunks of size interval
    #array must be          list   or  string type
    if type(array) not in [list, str]: return None
    
    superset=[]
    subset=[]
    for i,x in enumerate(array):
        subset.append(x)
        if len(subset) == interval or i+1 == len(array) : 
            superset.append(subset)
            subset=[]
    return [combine_fnc(element) for element in superset]

def unique_list(iterable): #reduces a list into a list of every unique value it contains
    return reduce(lambda ulist, val: ulist + ([val] if val not in ulist else []), iterable, [])

def make_hierarchial_dict(dataset, keys): #converts list of dict into hierarchial dict
    #dataset is list of dict, keys refers to attributes within each dict.
    #This function creates a structure where data is sorted first by key[0]
    error_msg='"dataset" must be list of dict, and "keys" must be list of keys from that dict'
    if type(dataset) != list or type(keys) != list: raise NameError(error_msg)
    if len(keys) == 0: return dataset #termination point of recursion
    
    result={} #construct first level of hiearchy
    for row in dataset: #linear scan through dataset, make dict from first search param
        if type(row) != dict and keys[0] in row.keys(): raise NameError(error_msg) #row must be type dict
        if row[keys[0]] not in result.keys(): result[row[keys[0]]] = []
        result[row[keys[0]]].append(row) #place row in dict
    
    #recursively construct lower levels of hierarchy
    for key, val in result.iteritems(): result[key]=make_hierarchial_dict(val, keys[1:])
    return result

def transpose(#transposes a list of lists
              matrix, # list of lists being transposed
              filler = []): #
    
    if type(matrix) != list and len(matrix) and unique_list(map(type, matrix)) == [list]:
        raise NameError("matrix parameter must be list of lists")
    if type(filler) != list: filler = [filler]

    return map(list, zip(*(filler + matrix)))
    
def sort(dataset, #list data structure to be sorted. may be list of dict or list of obj
         keys): #list of sort keys, prefix key with "!" for descending order
    #sorts a data set by one or more attributes, in ascending or descending for each
    if type(keys) != list or not len(keys): return dataset #termination point of recursion
    if unique_list(map(type, keys)) != [str] : raise NameError("keys must be list of strings")
    if type(dataset) != list or unique_list(map(type, dataset)) not in [[dict], [obj]]:
        raise NameError("dataset must be of type: list of dict, or list of obj")

    #exclamation flag reverses sort order
    if keys[0].startswith("!"): reverse = True; keys[0] = keys[0][1:]
    else: reverse = False
        
    entrytype = unique_list(map(type, dataset))[0]
    dataset = make_hierarchial_dict(map(dict, dataset), keys[:1]) #convert to list of dict, hash
    
    sorted_keys = sorted(dataset.keys(), reverse=reverse)
    sorted_dataset = [sort(dataset[key], keys[1:]) for key in sorted_keys]
    
    return map(entrytype, reduce(list.__add__, sorted_dataset, [])) # convert to orig type & concatenate 
#---------------------------------------------------------------------------------------------------    
#Functions for working with xml files  
def XMl_node(_name, _attr = {}, _txt = None, _parent=None, _children=[], **kwargs):
    if isinstance(_name, XML.Node): node = _name #update an already supplied node
    else: node = XML.Document().createElement(_name)
    
    if _txt: 
        node.appendChild(XML.Document().createTextNode(_txt))
    if type(_attr) == dict:
        for param, value in _attr.iteritems():
            node.setAttribute(param, str(value))
    for param, value in kwargs.iteritems():
        node.setAttribute(param, str(value))
    
    if isinstance(_parent, XML.Node): _parent.appendChild(node)
    if type(_children) == list:
        for child in _children:
            if isinstance(child, XML.Node): node.appendChild(child)
    
    return node     
    
    
#---------------------------------------------------------------------------------------------------    
#Functions and Classes for working with Binary files  

def asc2hex(ascval):  # converts each byte from 0x45 to "45", and place in array
    return ["%02X" % ord(x) for x in ascval]

def vals2hex(vals):
    result=""
    for row in chunk(vals,16): #16 bytes per row
        for val in row: result+=hex(val)[2:].rjust(2,"0").upper() + " "
        result+="\n"
    return result[:-2] #strip off last space and carriage return

class BufferType_RW (): #a class for creating and interpretting binary data from python data types
    def __init__(self, val="", bigend=True, start=0):
        if type(bigend) != bool: raise NameError('endianness type is not a boolean')
        
        self.rw_index = 0
        self.end=">" if bigend else "<"
        self.start=start
        
        #inputs
        if type(val) == str: 
            if re.match("\A([0-9A-F]{2}\s)+[0-9A-F]{2}\Z", trim(val)): #whitespace delimited hex bytes
                val = "".join([chr(eval("0x" + byte)) for byte in re.split("\s+", trim(val))])
            self.val = str(val)
        elif isinstance(val, BufferType_RW): #user passed in an instance of BufferTypeRW
            self = val # copy instance to self
            
        elif type(val) == list and type(val[0]) == int:
            try: self.val = "".join([chr(x) for x in val])
            except: raise NameError('Initialization Value is not a String')
        else: raise NameError('Initialization Value is not a String')
        
        self.end=">" if bigend else "<"
        self.start=start
        
        self.KnownVarTypes=set('Uint8,Uint16,Uint32,Int8,Int16,Int32,String,\
                                Enum,Bool,Pad,Float32,Float64'.split(","))
    
    # add methods for basic python functions
    def __len__(self): return len(self.val) #allows for len(self)
    def __str__(self): return self.val #allows for str(self)
    def __repr__(self): return repr(self.val) #allows for repr(self)
    #def __getattr__(self, name): return self.val[name] #allows for x= self[name]
    #def __setattr__(self, name, value): self.val[name] = value #allows for self[name]=value
    
    # overload '+' operator
    def __add__(self,other): # allow for concatenation of buffers
        result = copy.copy(self)
        result.val += str(other)
        result.rw_index=result.length()
        return result        

    # internal-use only methods
    def __packunpack(self, fmt, value=None, insert = True):
        if value != None: #pack
            if type(value) == list: #if argument is list, iterate over each element
                for x in value: self.__packunpack(fmt, x)
            else: 
                packedresult="".join(list(struct.pack(self.end + fmt, value)))
                before, after = self.val[:self.rw_index], self.val[self.rw_index:]
                
                if insert: self.val = before + packedresult + after #insert packedresult
                else: self.val = before + packedresult + after[len(packedresult):]#insert as an overwrite
                
                if insert: self.rw_index+=len(packedresult) #else don't move ptr
                
        else: #unpack
            return list(struct.unpack(self.end + fmt, self.GetVals(struct.calcsize(fmt))))[0]

    #External methods 
    def GetVals(self, num_bytes): #returns a string of length num_bytes, start at ptr
        if self.rw_index + num_bytes <= self.length:
            result=self.val[self.rw_index: self.rw_index+num_bytes]
            self.rw_index+=num_bytes
            return result
        else: return None
    
    def String(self, arg, null_bytes = 1):
        if type(arg) == str:#write to self
            self.val +=arg + "\0"*null_bytes
            self.rw_index+=len(arg) + null_bytes
        elif type(arg) == int: #read from self
            self.rw_index+=arg
            return self.val[self.rw_index- arg:self.rw_index]
            
        else: raise NameError("String Method needs int to unpack chars, or string to pack")
    
    def Buffer(self, num_bytes, offset=0):
        self.rw_index+=offset
        start=self.start + self.rw_index
        endtypeconv={">":True, "<":False}
        return BufferType_RW(self.GetVals(num_bytes), endtypeconv[self.end], start)
    
    def align(self,numbytes): self.val+= "\0" * (self.length() % numbytes)
    def consume_pad(self, alignment): #skips ptr ahead to consume pad bytes
        if self.rw_index % alignment: 
            self.rw_index += alignment - self.rw_index % alignment
    
    
    #Get info about buffer
    def ptr(self): return self.start+self.rw_index #current memory position
    def length(self): return len(self.val) 
    def num_Bytes_Left(self): return self.length() - (self.rw_index)
    def Leftover_Bytes(self): return self.String(self.num_Bytes_Left(),0)

    def HexDump(self, num_bytes = None, include_header = True, bytes_per_row = 16):
        if include_header:
            result = "Offset: " + " ".join(["%02X" % x for x in range(bytes_per_row)])
            result += "\n" + "0x".center(8," ") + "-"*(3*bytes_per_row) + "\n"
        else: result = ""
        
        start = self.rw_index
        
        if num_bytes and type(num_bytes) == int: 
            bytes_to_print = self.String(num_bytes, 0)
        elif num_bytes == None: bytes_to_print = self.String(self.num_Bytes_Left(),0)
        else: raise NameError("num_bytes param must be int or None")
        
        for i,row in enumerate(chunk(bytes_to_print, bytes_per_row)):    
            result+= "%06X" % (start + i*bytes_per_row) + ": "
            for char in row: result += "%02X " % ord(char)
            result+="\n"
        return result
            
    def Pad24(self, *args): return self.String("\0\0\0" if len(args) else 3, null_bytes = 0)
            
    #add stubs for some basic varible types
    def Uint8(self, value=None, insert = True): return self.__packunpack("B", value, insert = insert)
    def Uint16(self, value=None, insert = True): return self.__packunpack("H", value, insert = insert)
    def Uint32(self, value=None, insert = True): return self.__packunpack("I", value, insert = insert)
    def Int8(self, value=None, insert = True): return self.__packunpack("b", value, insert = insert)
    def Int16(self, value=None, insert = True): return self.__packunpack("h", value, insert = insert)
    def Int32(self, value=None, insert = True): return self.__packunpack("i", value, insert = insert)
    def Float32(self, value=None, insert = True): return self.__packunpack("f", value, insert = insert)
    def Float64(self, value=None, insert = True): return self.__packunpack("d", value, insert = insert)
    def Bool(self, value=None, insert = True): return self.__packunpack("?", value, insert = insert)
    
    def BitField(self, value = None, insert=True): #converts list of bool to/from buf
        if type(value) == list: #array of bits to pack
            #value = [False] * (len(value) % 8) + value
            for byte in lib.chunk(value, 8):
                self.Uint8(sum([2**i if bit else 0 for i, bit in enumerate(byte)]))
        elif type(value) == int: #number of bytes to unpack
            all_bits = []
            for i in range(value):
                bits = [int(bit) for bit in bin(self.Uint8())[2:]]
                all_bits += [0] * (8 - len(bits) % 8) + bits #left-pad, push
            return all_bits
    
    def By_VarType(self, var_type, value=None): #calls a var method using a variable 
        # ie: self.By_VarType("Uint32") is the same as self.Uint32()
        if var_type in self.KnownVarTypes: return getattr(self, var_type)(value)
        else: raise NameError("%s")
    
    
    def HexString(self, num): return "0x" + "".join(["%02X"%ord(x) for x in self.GetVals(num)])
    
    def CRC16(self, polynomial): 
        crc=reduce(self.val, lambda x,y: x^polynomial(y))

        #crc=binascii.crc32(self.val) & 0xFFFFFFFF #get CRC32
        self.Uint16(crc) # insert CRC32 at position of rw_index
        return "0x%08X" % crc #return CRC in hex string format
    
    def CRC32(self): 
        crc=binascii.crc32(self.val) & 0xFFFFFFFF #get CRC32, uses Ethernet standard for CRC
        self.Uint32(crc) # insert CRC32 at position of rw_index
        return "0x%08X" % crc #return CRC in hex string format
    
def floatstrip(x):
    return str(int(x)) if float(x) == int(float(x)) else str(x)

#---------------------------------------------------------------------------------------------------    
#Code for working with Excel Files

def convXLRowColRef(XLref): #converts the excel row/col notation to integers for each
    #Reads in a cell reference in the format of a letter(s) then numbers, as is used in excel
    #returns a dictionary object 
    [row,col]=[0,0] #initialize values
    for x in XLref.lower():
        if str.isalpha(x): col = col * 26 + (ord(x) - 96)
        elif str.isdigit(x): row = row * 10 + int(x)
    #row/col is a 1-based reference and row0/col0 being 0-based
    return {'row':row, 'col':col, 'row0':row-1, 'col0':col-1} 

class libcsv:
    def __init__(self,filename, method, suffix=""):
        import csv
        self.suffix=("-" + suffix) if len(suffix) else suffix
        self.filename=".".join(filename.split(".")[:-1]) + self.suffix + "." + filename.split(".")[-1]
        self.method=method
        self.delim={'csv':',', 'tsv':'\t'}.get(filename.lower().split(".")[-1], ',')
        
        
        self.ws = [[""]]
        if type(method) == str and 'r' in method: #if using read method
            try: 
                self.ws = list(csv.reader(open(self.filename, 'r'), delimiter=self.delim)) #see if file exists, read text
                self.filefound = True
            except: self.filefound = False
            
            
        
        self.nrows = len(self.ws) # the num of rows
        self.ncols = sorted([len(row) for row in self.ws])[-1] # the largest num of cols
        
        for iR, row in enumerate(self.ws): # iterate over every row
            for iC, cell in enumerate(row): # iterate over every cell in the row
                try: self.ws[iR][iC]=eval(cell, {}, {}) #convert string to python types if possible
                except: pass #leave cell as-is, for string and unicode types
        
        self.wb={} #place sheet into wb collection

        
    def sheet_by_name(self, sheetname): return self.add_sheet(sheetname) #for reading
    
    def add_sheet(self, sheet_name): #for writing
        self.wb[sheet_name] = libcsv(self.filename, self.method, sheet_name)
        return self.wb[sheet_name]
    
    def cell_value(self, row, col):
        if not self.filefound: raise NameError("File Not Found: %s" % self.filename) 
        if row < len(self.ws) and col < len(self.ws[row]): return self.ws[row][col]
        else: return ''
    
    def write(self, row, col, value,fmt=None):
        if row >= len(self.ws): self.ws += [[''] for x in range(row - len(self.ws) + 1)]
        if col >= len(self.ws[row]): self.ws[row] += ['' for x in range(col - len(self.ws[row]) + 1)]
        
        self.ws[row][col] = value
        
    def save(self, filename=''):
        filename = self.filename

        
        if self.ws != [['']]:
            delim={'csv':',', 'tsv':'\t'}.get(filename.lower().split(".")[-1], ',')
            
            for iR, row in enumerate(self.ws): # iterate over every row
                for iC, cell in enumerate(row): # iterate over every cell in the row
                    if type(cell) not in [str, unicode]: #if not string or unicode
                        self.ws[iR][iC] = repr(cell) #convert python types -> string
                    elif cell != '': self.ws[iR][iC] = '"%s"' % cell # leave as string
            
            #convert 2D table to string and encode to ascii file
            write_file_and_save(filename, "\n".join([self.delim.join(row) for row in self.ws]), 'w')
            
        for name, sheet in self.wb.iteritems():
            sheet.save()
    
    
class readExcelTable: #reads the rows in excel files as dictionary objects based on filename and sheet name
    def __init__(self,filename):#initializes class
        
        if filename.lower().split(".")[-1] in ['xls']: 
            import xlrd
            self.wb=xlrd.open_workbook(filename) #open an excel workbook
        else: self.wb=libcsv(filename, 'r') #to read CSV files
        

        self.iR={}#row read pointer
        self.ws={}#stores the worksheet
        self.info={}#contains sheet info (nrows, ncols, rowstart, colstart, colheads)
        self.data={}#stores an (list of dict) for each sheetEPC
        self.objdata={} #a dictionary by sheet of a list of objects
        self.array={} # a dictionary by sheet of a 2D array of the sheet's data.
        
    def listOfSheets(self): return map(str, self.wb.sheet_names()) #returns a list of all the sheet names
    
    def readSheet(self,sn,rowstart=0,colstart=0, row_type=dict): #reads in a sheet into self.data
        if row_type not in [dict, list, obj, object]: raise NameError("row_type may only be list, dict, or obj")
        
        if row_type == object: row_type = obj # obj and object are synonymous here.
        
        self.ws[sn]=self.wb.sheet_by_name(sn)#get worksheet from xlrd
        self.info[sn]={}
        self.info[sn]['rowstart']=rowstart
        self.info[sn]['colstart']=colstart
        info=self.getInfo(sn)
        
        self.data[sn]=[]
        
        self.array[sn]=[]
        
        #extract in list of lists format
        for iR in range(rowstart,info['rows']):
            #construct 2D array of data
            self.array[sn].append([self.ws[sn].cell_value(iR,iC) for iC in range(info['cols'])])
            
        #extract in list of dict format
        if row_type in [obj, dict]:
            for iR in range(rowstart + 1,info['rows']):
                line={}#store a dict of each element
                for iC in range(info['cols']): 
                    if info['colheads'][iC] != "": 
                        line[info['colheads'][iC]]=self.ws[sn].cell_value(iR,iC)
                self.data[sn]+=[line]#add line to data
            
        self.objdata[sn]=map(obj, self.data[sn]) #create list of obj format
        
        self.iR[sn]=1#initialize read pointer to first row
        return {list: self.array[sn], dict: self.data[sn], obj: self.objdata[sn]}[row_type] 
        
    def readCell(self, sn, *args): #read a cell or multiple cells using a couple different methods
        if sn not in self.info.keys(): self.readSheet(sn) #read sheet if hasn't been yet

        def is_row_col(row, col): return (type(row) == int and type(col) == int)
        
        #if args == (row, col); return value
        if len(args)==2 and is_row_col(*args): 
            return self.ws[sn].cell_value(*args)
        #or if args[0] == (row, col); return value
        elif len(args)==1 and type(args[0]) == tuple and is_row_col(*args[0]):
            return self.readCell(sn, *args[0])
        #or if args[0] == {key1: (row1,col1), etc...}; return {key1: value1, etc...}
        elif len(args)==1 and type(args[0]) == dict:
            return dict([(key,self.readCell(sn, *RwCl)) for key, RwCl in args[0].iteritems()])
        # or if args[0] == list of rows #'s and args[1] == col ; return list of values
        elif len(args) == 2 and type(args[0]) == list and type(args[1]) == int:
            return [self.readCell(sn, row, args[1]) for row in args[0]]
        # or if args[0] == row and args[1] == list of col #'s ; return list of values
        elif len(args) == 2 and type(args[0]) == int and type(args[1]) == list:
            return [self.readCell(sn, args[0], col) for col in args[1]]
        # or if args[0] == list of row #'s and args[1] == list of col #'s ; return 2D list of values
        elif len(args) == 2 and type(args[0]) == list and type(args[1]) == list:
            return [self.readCell(sn, row, args[1]) for row in args[0]]
    
    
    def getInfo(self,sn):#returns a dict of the sheet's info
        info=self.info[sn]
        info['rows']=self.ws[sn].nrows
        info['cols']=self.ws[sn].ncols
        info['colheads']=[]
        for iC in range(info['colstart'], info['cols']): 
            info['colheads']+=[str(self.ws[sn].cell_value(info['rowstart'],iC))]
        
        self.info[sn].update(info)
        return info
    def readLine(self,sn):#returns dict at read pointer, post increment
        line=self.data[sn][self.iR[sn]]
        self.iR[sn]+=1
        return line
    def findLine(self,sn,params,findall=False): #finds a line that matches certain params, returns dict
        results=[]
        for line in self.data[sn]:
            paramsMatchesLine=True
            for key,val in params.iteritems(): 
                if line[key] != val: paramsMatchesLine=False
            if paramsMatchesLine==True: 
                results+=[line]
                if findall==False: return line
        return results if findall else {}
        
class createExcelTable: #creates an Excel table 
    def __init__(self,filename): #initialize workbook
        try: self.filename=checkpath(filename) #checks path and stores the filename for later
        except: raise NameError("Invalid Path")
        

        self.iR={}#store the write pointer
        self.iRstart={} #dict of integer
        self.ws={}#the collection of worksheets
        self.cols={}#
        self.col_fmts={}
        
        self.formats={} #dict of formats, key is sorted lower-case easyxf text of format
        self.zebra_fmt={} #dict of list of formats, assignes zebra pattern to data area
        
        if filename.lower().split(".")[-1] in ['xls']: 
            import xlwt
            self.wb=xlwt.Workbook() #creates a workbook
            self.filetype = 'xls'
        else:
            self.wb=libcsv(filename, 'w') #to handle CSV files
            self.filetype = 'sv'        
        
    def newSheet(self,sn,colheads=None, iRstart=0, zebra_fmt=[""], col_formats={},
                 header_fmt="font: bold on; pattern: pattern solid, fore-colour light-green; align: wrap on"): 
        self.iRstart[sn]=iRstart
        self.col_fmts[sn]=col_formats
        
        
        for i,val in enumerate(zebra_fmt): 
            zebra_fmt[i]=("pattern: pattern solid, fore-colour " + val) if len(val) else ""
        self.zebra_fmt[sn]=zebra_fmt
        
        #create a new sheet, named sn, with colheads in row 0
        self.ws[sn]=self.wb.add_sheet(sn)#adds a new sheet
        self.cols[sn]={}#container for column heading indecies
        if colheads != None:
            for i, colhead in enumerate(colheads): 
                self.cols[sn][colhead]=i
                if colhead not in self.col_fmts[sn]: self.col_fmts[sn][colhead] = ""
                self.writeCell(sn, iRstart,i, colhead, #write the column headings in row 0
                               self.__combine_fmts(header_fmt, self.col_fmts[sn][colhead]))
            
            if self.filetype == 'xls':
                self.ws[sn].row(iRstart).set_style(self.__formatting(header_fmt))
        
        self.iR[sn]=iRstart+1#initialize the write pointer
        return self
        
    def writeLine(self,sn,data,formatting=""): #writes a line of data given a dict, the keys match those in colheads
        if type(formatting) != str: raise NameError("formatting param may only be a string")
        
        if type(data) == dict:
            row_fmt_str=self.zebra_fmt[sn][(self.iR[sn] - self.iRstart[sn] - 1) % len(self.zebra_fmt[sn])]
            if self.filetype == 'xls':
                self.ws[sn].row(self.iR[sn]).set_style(self.__formatting(row_fmt_str))
            
            formatting=self.__combine_fmts(row_fmt_str, formatting)#concatenate row and supplied formats
            
            
            for key, val in data.iteritems(): #write each value to its corresponding col
                if key in self.cols[sn]: 
                    if key in self.col_fmts[sn]: 
                        cell_formatting=self.__combine_fmts(formatting, self.col_fmts[sn][key])
                    else: cell_formatting=formatting
                    self.writeCell(sn, self.iR[sn],self.cols[sn][key],val,cell_formatting)
                
            
            self.iR[sn]+=1 #increment the write pointer
        elif type(data) in [obj, object]: #is lib.obj type
            self.writeLine(sn, line, dict(data), formatting)
        
        elif type(data) == list:
            for line in data: self.writeLine(sn, line,formatting)
        
        else: raise NameError("Invalid data type for data parameter")
        
        return self    
    def writeCell(self,sn,row,col,val,formatting="", rowspan=1, colspan=1): 
        #writes a cell/row/table given a sheet, row, & col
        if type(formatting) != str: raise NameError("formatting param may only be a string")
        if type(rowspan) != int or rowspan < 1: rowspan = 1
        if type(colspan) != int or colspan < 1: colspan = 1
        
        if type(val) == tuple: #val is tuple, style is val = (value_to_write, formatting)
            val,cell_fmt = val #unpack format string from tuple
            
            cell_fmt=self.__combine_fmts(formatting, cell_fmt)
            self.writeCell(sn,row,col,val,cell_fmt) #unpack tuple through recursion
        elif type(val)==list: #val is list
            if unique_list(map(type, val)) != [list]: #val is 1D list, interpret as table
                for i,v in zip(range(len(val)),val): self.writeCell(sn,row,col+i,v,formatting)
            else: #val is 2D list, interpret as row
                for i,v in zip(range(len(val)),val): self.writeCell(sn,row+i,col,v,formatting)
        elif type(val) in [unicode,str, int, float, bool]: #val is string, int, float, or bool
            if type(val) in [unicode, str] and val.startswith('=') and self.filetype == 'xls': #if this is a text formula
                import xlwt
                val=xlwt.Formula(val[1:]) #convert to formula type
            
            if rowspan > 1 or colspan > 1:
                self.ws[sn].write_merge(row, row + rowspan - 1, #a rowspan of 1 means 1 row
                                        col, col + colspan - 1, #a colspan of 1 means 1 col
                                        val, self.__formatting(formatting)) #make call to xlwt layer
            else:
                self.ws[sn].write(row,col,val, self.__formatting(formatting)) #make call to xlwt layer
        elif type(val)==type(None): pass #don't write to the cell
        elif type(val)==dict: #val is dictionary, coder was dumb and used wrong class method
            raise NameError("use writeLine instead of writeCell method when passing a dict object")
        else: raise NameError("Could not recognize Val parameter")
        
        return self
    def __combine_fmts(self,*args):
        fmt_list=[]
        for x in args: 
            for y in x.split(";"):
                if len(trim(y)): fmt_list+=[trim(y)]
        return "; ".join(fmt_list)
        
    def __formatting(self,fmt_string):
        if self.filetype == 'sv': return object()
        
        from xlwt import easyxf, Style
        
        if isinstance(fmt_string, Style.XFStyle): return fmt_string
        elif type(fmt_string) == str: 
            
            #check to see if formatting has been used before
            if fmt_string not in self.formats.keys(): #add formatting object to dictionary
                self.formats[fmt_string]= easyxf(fmt_string)
            return self.formats[fmt_string]
        else: raise NameError("fmt_string param couldn't be recognized")
    
    def ColWidth(self, sn, widths):
        if sn not in self.ws.keys(): raise NameError("Sheet not Found")
        if not isinstance(widths, dict): raise NameError("Widths param not dict")
        for col,width in widths.iteritems(): 
            if type(col) == str: col=self.cols[sn][col]
            if self.filetype == 'xls': self.ws[sn].col(col).width=int(width*36.3)
        
    def GetWSobject(self, sn): return self.ws[sn] # returns ws object for any optional xlwt formatting
    def List_Colors(self):
        if self.filetype == 'sv': return []
        import xlwt
        return sorted(xlwt.Style.colour_map.keys())
    
    def close(self): #save and close the workbook
        import time
        try: self.wb.save(self.filename)
        except:
            print "Could not save %s." % self.filename
            print "Is this file open somewhere? Close to continue"
            while True:
                try:
                    self.wb.save(self.filename)
                    break
                except: time.sleep(.1) #slow the app down to checking every 100ms to conserve CPU
#---------------------------------------------------------------------------------------------------    
#File I/O

headertxt="""/*
********************************************************************************
                                  WARNING
   Copyright %s Hamilton Sundstrand Corporation. This document is the
   property of Hamilton Sundstrand Corporation ("HSC"). You may not possess,
   use, copy or disclose this document or any information in it, for any 
   purpose, including without limitation, to design, manufacture or repair 
   parts, or obtain any government approval to do so, without HSC's express 
   written permission. Neither receipt nor possession of this document
   alone, from any source, constitutes such permission. Possession, use,
   copying or disclosure by anyone without HSC's express written permission
   is not authorized and may result in criminal and/or civil liability.
********************************************************************************

                       ********************************
                       *                              *
                       *%s*
                       *                              *
                       ********************************

Author(s):  %s
********************************************************************************
Description:

%s

********************************************************************************
Revision History
*******************************************************************************/
/*$Log:$ */

/******************************************************************************/\n\n\n
""" # C-code header
def write_file_and_save(filename, data, write_method = 'w'):
    filename = filename.replace("/", "\\")
    try:
        f=open(checkpath(filename), write_method)
        f.write(str(data))
        f.close()
        return True
    except: return False

def read_file_and_close(filename, read_method = 'r'): 
    filename = filename.replace("/", "\\")
    try: return open(filename, read_method).read()
    except: return False

def C_header(filename, author, desc, content=""):
    module = filename.lower().split(".")[0].upper()
    result = headertxt % (time.strftime("%Y"), filename.center(30), author, desc) #add header
    if filename.lower().split(".")[-1] == 'h': #if this is a header file, add macro
        result += "#ifndef %s\n#define %s\n\n" % (module, module) + content + "\n#endif"
    else: result += content

    return result
#written by Josh Deblon    
def file_is_empty(path):
    import os
    return os.stat(path).st_size==0
